package com.qisi.utils;

import lombok.extern.slf4j.Slf4j;

import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.sql.*;
import java.util.ArrayList;
import java.util.regex.Matcher;
import java.util.regex.Pattern;


/**
 * Mysql 工具类，功能：
 *      1、onceSelectToJavaBean 方法通过类调用，执行一次查询服务
 *      2、sqlToJavaBean 方发通过实例对象调用，结束时调用 {@link #close}
 *
 */
@Slf4j
public class MysqlConnectionUtil {

    private Connection conn;

    public MysqlConnectionUtil(String url, String user, String pswd) throws SQLException{
        //Class.forName(Config.MYSQL_DRIVER_CLASS);
        this.conn = DriverManager.getConnection(url, user, pswd);
    }


    /**
     * rs 转 javaBean
     * 注意：属性名必须严格遵守驼峰命名，转成下划线在数据库有对应字段
     * @bean T 为 JavaBean 对象类型
     */
    private <T> T resultToBean(ResultSet rs, Class<T> bean) {
        T result = null; // 封装数据完返回的Bean对象

        try {
            // 无参构造 创建bean对象
            result = bean.newInstance();

            // 获得bean的属性数组
            Field[] fields = bean.getDeclaredFields();

            Pattern humpPattern = Pattern.compile("[A-Z]");
            for (Field field : fields) {

                // 获取属性名
                String fieldName = field.getName();

                // 属性名转数据库字段，驼峰转下划线

                Matcher matcher = humpPattern.matcher(fieldName);
                StringBuffer sb = new StringBuffer();

                while (matcher.find()) {
                    matcher.appendReplacement(sb, "_" + matcher.group(0).toLowerCase());
                }

                matcher.appendTail(sb);
                String sqlName = sb.toString();

                // 用属性名得出set方法，set+将首字母大写
                String setMethodName = "set" + fieldName.substring(0, 1).toUpperCase() + fieldName.substring(1);

                // 获取字段类型
                String type = field.getType().toString();

                Object object = rs.getObject(sqlName);

                // 通过调用set方法进行属性注入
                switch (type) {
                    case "int": {
                        // 类型匹配 则获取对应属性的set方法    方法名，类型
                        Method method = bean.getMethod(setMethodName, int.class);
                        // 执行set方法    被执行的对象，需要注入的数据
                        method.invoke(result, object);
                        break;
                    }
                    case "class java.lang.String": {
                        Method method = bean.getMethod(setMethodName, String.class);
                        method.invoke(result, object);
                        break;
                    }
                    case "class java.lang.Double": {
                        Method method = bean.getMethod(setMethodName, Double.class);
                        method.invoke(result, object);
                        break;
                    }
                    case "class java.sql.Timestamp": {
                        Method method = bean.getMethod(setMethodName, Timestamp.class);
                        method.invoke(result, object);
                        break;
                    }
                    default:
                        throw new RuntimeException("类型异常");
                }
            }
        } catch (Exception e) {
            log.error("ResultSet: {} can not cast to  {}",  rs, bean);
            e.printStackTrace();
        }

        return result;
    }

    /**
     * sql 转 JavaBean
     */
    public <T> ArrayList<T> sqlToJavaBean(String sql, Class<T> bean) throws SQLException {

        PreparedStatement stmt = conn.prepareStatement(sql);
        ResultSet resultSet = stmt.executeQuery();

        ArrayList<T> arrayList = new ArrayList<>();
        while (resultSet.next()) {
            T t = resultToBean(resultSet, bean);
            arrayList.add(t);
        }
        stmt.close();
        return arrayList;
    }

    /**
     * close Connection
     */
    public void close() throws SQLException {
        if(this.conn != null && !this.conn.isClosed()){
            conn.close();
        }
    }

    /**
     * 对于给定 sql 一次读取 mysql 中的数据，转为 Class<T> 格式
     * @param sql 要查询的 SQL
     * @param bean 数据库中表对应的 JavaBean 对象(属性要求必须驼峰命名)
     * @param <T> JavaBean 对象类型
     */
    public <T> ArrayList<T> onceSelectToJavaBean(String sql, Class<T> bean) throws SQLException {

        ArrayList<T> resList = this.sqlToJavaBean(sql, bean);
        this.close();

        return resList;
    }

}
